﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
using System.Data.SqlClient;
using DigDes.DS2012.Infrastructure;
using System.Data;

namespace DigDes.DS2012.DataAccess
{
    /// <summary>
    /// Базовый класс для классов слоя доступа к данным
    /// </summary>
    public class DataAccessBase : IDisposable
    {
        private const int SQL_ERR_DEADLOCK_VICTIM = 1205;

        private SqlConnection _connection = AppConfig.GetConnection();
        
        /// <summary>
        /// Возращает объект, подготовленный для вызова хранимой процедруы
        /// </summary>
        /// <param name="storedProcedureName">Имя вызываемой хранимой процедуры</param>
        /// <returns>Объект SqlCommand, готовый к выполнению хранимой процедуры <ref para="storedProcedureName" /></returns>
        protected SqlCommand CreateSPCommand(string storedProcedureName, bool autoOpenConnection = true)
        {
            var command = _connection.CreateCommand();
            command.CommandType = CommandType.StoredProcedure;
            command.CommandText = storedProcedureName;
            command.CommandTimeout = AppConfig.CommandTimeout;

            /*
            var strConBuilder = new SqlConnectionStringBuilder();
            strConBuilder.DataSource = "(local)";
            strConBuilder.IntegratedSecurity = true;
            strConBuilder.InitialCatalog = "DS2012";
            _connection.ConnectionString = strConBuilder.ConnectionString;
            */

            if (autoOpenConnection)
            {
                _connection.Open();
            }
            return command;
        }

        /// <summary>
        /// Возращает объект, подготовленный для вызова хранимой процедруы
        /// </summary>
        /// <param name="storedProcedureName">Имя вызываемой хранимой процедуры</param>
        /// <param name="sqlParameters">Список параметров хранимой процедуры</param>
        /// <returns>Объект SqlCommand, готовый к выполнению хранимой процедуры <ref para="storedProcedureName" /></returns>
        protected SqlCommand CreateSPCommand(string storedProcedureName, SqlParameter[] sqlParameters, bool autoOpenConnection = true)
        {
            var command = _connection.CreateCommand();
            command.CommandType = CommandType.StoredProcedure;
            command.CommandText = storedProcedureName;
            command.CommandTimeout = AppConfig.CommandTimeout;
            command.Parameters.AddRange(sqlParameters);

            /*
            var strConBuilder = new SqlConnectionStringBuilder();
            strConBuilder.DataSource = "(local)";
            strConBuilder.IntegratedSecurity = true;
            strConBuilder.InitialCatalog = "DS2012";
            _connection.ConnectionString = strConBuilder.ConnectionString;
            */

            if (autoOpenConnection)
            {
                _connection.Open();
            }
            return command;
        }

        /// <summary>
        /// Запустить группу команд в рамках транзакции
        /// </summary>
        /// <param name="transactionScript"></param>
        /// <param name="autoCommit"></param>
        /// <param name="retryCount"></param>
        public T RunInTransaction<T>(Func<T> transactionScript, bool autoCommit = true, int retryCount = 2) where T : class
        {
            T result = null;
            int tries = 0;
            while (tries < retryCount)
            {
                // Любые команды connection.Open автоматически занесут эти соединения
                //  в список соединений данной транзакции.
                using (var transaction = new TransactionScope())
                {
                    try
                    {
                        result = transactionScript.Invoke();
                        if (autoCommit)
                        {
                            transaction.Complete(); // Если не вызвать этот метод, по выходу за using транзакция откатится
                        }
                        break;
                    }
                    catch (SqlException ex)
                    {
                        if (ex.Number == SQL_ERR_DEADLOCK_VICTIM) // Произошла взаимоблокировка, 
                            //  наш процесс закрыли выбрав в качестве "жертвы". 
                            //  Пытаемся повторить действие заново открыв физическое соединение с БД.
                        {
                            RecreateConnection();
                            tries++;
                            if (tries >= retryCount)
                                throw;
                            else
                                continue;
                        }

                        throw;
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Вернуть соединение с БД в пул соединений.
        /// </summary>
        protected void CloseConnection()
        {
            if (_connection != null && _connection.State != ConnectionState.Closed)
            {
                _connection.Close();
            }
        }

        protected void RecreateConnection()
        {
            CloseConnection();
            SqlConnection.ClearPool(_connection); // Закрываем физическое соединение с БД
            _connection.Dispose();            

            _connection = AppConfig.GetConnection();
        }

        #region IDisposable Members

        public void Dispose()
        {
            CloseConnection();
            _connection.Dispose();
        }

        #endregion
    }
}
